﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ignitron.EEngine.Maths;

namespace Ignitron.EEngine.Model
{
    /// <summary>
    /// Model structure - composition of several meshes
    /// </summary>
    public class EModel
    {
        /// <summary>
        /// The list of meshes
        /// </summary>
        protected List<EMesh> mMeshes = new List<EMesh>();

        /// <summary>
        /// Protect from creating outside assembly
        /// </summary>
        internal EModel()
        {
        }

        /// <summary>
        /// Property getter for meshes
        /// </summary>
        public IEnumerable<EMesh> Meshes
        {
            get { return mMeshes; }
        }

        /// <summary>
        /// Creates a new mesh. 
        /// </summary>
        public EMesh AddMesh()
        {
            EMesh mesh = CreateMesh();
            mMeshes.Add(mesh);
            return mesh;
        }

        /// <summary>
        /// Factory method for creating a mesh
        /// </summary>
        protected virtual EMesh CreateMesh()
        {
            return new EMesh();
        }

        /// <summary>
        /// Flip texture's U coordinate
        /// </summary>
        public void FlipTextureX()
        {

            foreach (var mesh in mMeshes)
            {
                EVector2[] texcoords = mesh.TexCoords.ToArray();
                
                for (int i = 0; i < texcoords.Length; i++)
                {
                    texcoords[i].X = 1f - texcoords[i].X;                    
                }

                mesh.SetTexCoords(texcoords);
            }
        }

        /// <summary>
        /// Normalizes the mesh
        /// </summary>
        public void Normalize()
        {
            EBoundingBox bb = GetBoundingBox();
            float rangeX = bb.MaxX - bb.MinX;
            float rangeY = bb.MaxY - bb.MinY;
            float rangeZ = bb.MaxZ - bb.MinZ;

            float maxRange = rangeX;
            maxRange = Math.Max(maxRange, rangeY);
            maxRange = Math.Max(maxRange, rangeZ);

            float factor = 1 / (maxRange / 2);

            float tranX = -(rangeX * factor / 2) - (bb.MinX * factor);
            float tranY = -(rangeY * factor / 2) - (bb.MinY * factor);
            float tranZ = -(rangeZ * factor / 2) - (bb.MinZ * factor);
            EVector3 tranVector = new EVector3(tranX, tranY, tranZ);

            foreach (var mesh in mMeshes)
            {
                for (int i = 0; i < mesh.Vertices.Length; i++)
                {
                    mesh.Vertices[i] *= factor;
                    mesh.Vertices[i] += tranVector;
                    var vec = mesh.Vertices[i];
                    mesh.Vertices[i] = new EVector3(vec.X, vec.Y, vec.Z);
                }
            }

            CalculateNormals();
        }


        /// <summary>
        /// Method used for flipping normals
        /// TO-DO: What is this good for?
        /// </summary>
        protected void FlipNormals()
        {
            foreach (var mesh in mMeshes)
            {
                for (int i = 0; i < mesh.Normals.Length; i++)
                {
                    var norm = mesh.Normals[i];
                    mesh.Normals[i] = new EVector3(norm.X, norm.Y, norm.Z);
                } // for
            }
        }


        /// <summary>
        /// Calculates the normal vectors for the meshes
        /// </summary>
        public void CalculateNormals()
        {
            foreach (var mesh in mMeshes)
            {
                List<List<EVector3>> tmpNormals = new List<List<EVector3>>();
                for (int i = 0; i < mesh.Vertices.Length; i++)
                    tmpNormals.Add(new List<EVector3>());

                for (int face = 0; face < mesh.Indices.Length; face += 3)
                {
                    uint vec1 = mesh.Indices[face];
                    uint vec2 = mesh.Indices[face + 1];
                    uint vec3 = mesh.Indices[face + 2];

                    EVector3 vecOne = mesh.Vertices[vec2] - mesh.Vertices[vec1];
                    EVector3 vecTwo = mesh.Vertices[vec3] - mesh.Vertices[vec1];
                    EVector3 norm = EVector3.Cross(vecOne, vecTwo);

                    tmpNormals[(int)vec1].Add(norm);
                    tmpNormals[(int)vec2].Add(norm);
                    tmpNormals[(int)vec3].Add(norm);
                }

                List<EVector3> normals = new List<EVector3>();
                foreach (var listNormals in tmpNormals)
                {
                    EVector3 normal = EVector3.Zero;
                    foreach (var n in listNormals)
                        normal += n;
                    normal.Normalize();
                    normals.Add(normal);
                }

                mesh.SetNormals(normals.ToArray());
            }
        }

        /// <summary>
        /// Returns the bounding box of the model
        /// </summary>
        /// <returns></returns>
        public EBoundingBox GetBoundingBox()
        {
            float minX = float.MaxValue;
            float minY = float.MaxValue;
            float minZ = float.MaxValue;
            float maxX = float.MinValue;
            float maxY = float.MinValue;
            float maxZ = float.MinValue;

            foreach (var mesh in mMeshes)
            {
                foreach (var vec in mesh.Vertices)
                {
                    maxX = Math.Max(maxX, vec.X);
                    maxY = Math.Max(maxY, vec.Y);
                    maxZ = Math.Max(maxZ, vec.Z);
                    minX = Math.Min(minX, vec.X);
                    minY = Math.Min(minY, vec.Y);
                    minZ = Math.Min(minZ, vec.Z);
                }
            }

            EBoundingBox bb = new EBoundingBox();
            bb.MinX = minX;
            bb.MinY = minY;
            bb.MinZ = minZ;
            bb.MaxX = maxX;
            bb.MaxY = maxY;
            bb.MaxZ = maxZ;

            return bb;
        }

    }
}
